import java.lang.classfile.ClassFile;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.lang.constant.ConstantDescs.CD_boolean;
import static java.lang.constant.ConstantDescs.CD_int;
import static java.lang.constant.ConstantDescs.CD_void;

public class TestClassAPI extends ClassLoader {
	public static boolean f1(int a) {
		System.out.println(a);
		return a != 0;
	}

	@SuppressWarnings("CodeBlock2Expr")
	public static void main(String[] args) throws Exception {
		var cf = ClassFile.of();
		var bytes = cf.build(ClassDesc.of("Test"), classBuilder -> {
			classBuilder.withMethod("m1", MethodTypeDesc.of(CD_int, CD_boolean),
							ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, mb -> {
								mb.withCode(cb -> cb // int m1(boolean b) { return (int)b; }
										.iload(cb.parameterSlot(0))
										.ireturn());
							})
					.withMethod("m2", MethodTypeDesc.of(CD_boolean, CD_int),
							ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, mb -> {
								mb.withCode(cb -> cb // boolean m2(int i) { return (boolean)i; }
										.getstatic(ClassDesc.of("java.lang.System"), "out", ClassDesc.of("java.io.PrintStream"))
										.iload(cb.parameterSlot(0))
										.invokestatic(ClassDesc.of("Test"), "m1", MethodTypeDesc.of(CD_int, CD_boolean))
										.invokevirtual(ClassDesc.of("java.io.PrintStream"), "println", MethodTypeDesc.of(CD_void, CD_int))
										.iload(cb.parameterSlot(0))
										.ireturn());
							});
		});

		var cls = new TestClassAPI().defineClass("Test", bytes, 0, bytes.length);
		var m2 = cls.getMethod("m2", int.class);
		System.out.println(" - " + (boolean)m2.invoke(null, 0)); // false
		System.out.println(" - " + (boolean)m2.invoke(null, 1)); // true
		System.out.println(" - " + (boolean)m2.invoke(null, 2)); // false
		System.out.println(" - " + (boolean)m2.invoke(null, -1)); // true
		System.out.println(" - " + (boolean)m2.invoke(null, -2)); // false
		System.out.println(" - " + (boolean)m2.invoke(null, 256)); // false
	}

	@SuppressWarnings("CodeBlock2Expr")
	public static void main1(String[] args) throws Exception {
		var cf = ClassFile.of();
		var bytes = cf.build(ClassDesc.of("Test"), classBuilder -> {
			classBuilder.withMethod("method", MethodTypeDesc.of(CD_void, CD_int.arrayType(), CD_int, CD_int),
					ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, mb -> {
						mb.withCode(cb -> cb // void swap(int[] a, int i, int j)
								.aload(cb.parameterSlot(0)) // a
								.iload(cb.parameterSlot(1)) // a,i
								.aload(cb.parameterSlot(0)) // a,i,a
								.iload(cb.parameterSlot(2)) // a,i,a,j
								.iaload()                   // a,i,a[j]
								.aload(cb.parameterSlot(0)) // a,i,a[j],a
								.iload(cb.parameterSlot(2)) // a,i,a[j],a,j
								.aload(cb.parameterSlot(0)) // a,i,a[j],a,j,a
								.iload(cb.parameterSlot(1)) // a,i,a[j],a,j,a,i
								.iaload()                   // a,i,a[j],a,j,a[i]
								.iastore()                  // a,i,a[j]   a[j]=a[i]
								.iastore()                  // a[i]=a[j]  a[j]=a[i]
								.return_());
					});
		});

		var cls = new TestClassAPI().defineClass("Test", bytes, 0, bytes.length);
		var method = cls.getMethod("method", int[].class, int.class, int.class);
		var a = new int[]{1, 2};
		method.invoke(null, a, 0, 1);
		System.out.println(a[0]);
		System.out.println(a[1]);

		Files.write(Path.of("Test.class"), bytes);
	}
}
